{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: http://mirrors.aliyun.com/pypi/simple/\n",
      "Requirement already satisfied: langchain-ollama in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (0.2.3)\n",
      "Requirement already satisfied: langchain-core<0.4.0,>=0.3.33 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-ollama) (0.3.39)\n",
      "Requirement already satisfied: ollama<1,>=0.4.4 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-ollama) (0.4.7)\n",
      "Requirement already satisfied: langsmith<0.4,>=0.1.125 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.33->langchain-ollama) (0.3.11)\n",
      "Requirement already satisfied: tenacity!=8.4.0,<10.0.0,>=8.1.0 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.33->langchain-ollama) (9.0.0)\n",
      "Requirement already satisfied: jsonpatch<2.0,>=1.33 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.33->langchain-ollama) (1.33)\n",
      "Requirement already satisfied: PyYAML>=5.3 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.33->langchain-ollama) (6.0.2)\n",
      "Requirement already satisfied: packaging<25,>=23.2 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.33->langchain-ollama) (24.2)\n",
      "Requirement already satisfied: typing-extensions>=4.7 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.33->langchain-ollama) (4.12.2)\n",
      "Requirement already satisfied: pydantic<3.0.0,>=2.5.2 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langchain-core<0.4.0,>=0.3.33->langchain-ollama) (2.10.6)\n",
      "Requirement already satisfied: httpx<0.29,>=0.27 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from ollama<1,>=0.4.4->langchain-ollama) (0.28.1)\n",
      "Requirement already satisfied: anyio in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from httpx<0.29,>=0.27->ollama<1,>=0.4.4->langchain-ollama) (4.8.0)\n",
      "Requirement already satisfied: certifi in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from httpx<0.29,>=0.27->ollama<1,>=0.4.4->langchain-ollama) (2025.1.31)\n",
      "Requirement already satisfied: httpcore==1.* in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from httpx<0.29,>=0.27->ollama<1,>=0.4.4->langchain-ollama) (1.0.7)\n",
      "Requirement already satisfied: idna in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from httpx<0.29,>=0.27->ollama<1,>=0.4.4->langchain-ollama) (3.10)\n",
      "Requirement already satisfied: h11<0.15,>=0.13 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from httpcore==1.*->httpx<0.29,>=0.27->ollama<1,>=0.4.4->langchain-ollama) (0.14.0)\n",
      "Requirement already satisfied: jsonpointer>=1.9 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from jsonpatch<2.0,>=1.33->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (3.0.0)\n",
      "Requirement already satisfied: orjson<4.0.0,>=3.9.14 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (3.10.15)\n",
      "Requirement already satisfied: requests<3,>=2 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (2.32.3)\n",
      "Requirement already satisfied: requests-toolbelt<2.0.0,>=1.0.0 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (1.0.0)\n",
      "Requirement already satisfied: zstandard<0.24.0,>=0.23.0 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from langsmith<0.4,>=0.1.125->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (0.23.0)\n",
      "Requirement already satisfied: annotated-types>=0.6.0 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (0.7.0)\n",
      "Requirement already satisfied: pydantic-core==2.27.2 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from pydantic<3.0.0,>=2.5.2->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (2.27.2)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from requests<3,>=2->langsmith<0.4,>=0.1.125->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (3.4.1)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from requests<3,>=2->langsmith<0.4,>=0.1.125->langchain-core<0.4.0,>=0.3.33->langchain-ollama) (2.3.0)\n",
      "Requirement already satisfied: sniffio>=1.1 in c:\\users\\rf.yin\\.conda\\envs\\pydantic-ai\\lib\\site-packages (from anyio->httpx<0.29,>=0.27->ollama<1,>=0.4.4->langchain-ollama) (1.3.1)\n"
     ]
    }
   ],
   "source": [
    "! pip install langchain-ollama"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'raw': AIMessage(content='{\\n  \"answer\": \"A pound of bricks is heavier than a pound of feathers.\",\\n  \"justification\": \"Both a pound of bricks and a pound of feathers weigh the same, one pound. The difference lies in their volume and density; bricks are much denser than feathers, meaning that while they weigh the same, the bricks will take up less space than the feathers. However, when comparing weight alone, both options weigh the same.\"\\n}', additional_kwargs={}, response_metadata={'model': 'qwen2.5:7b', 'created_at': '2025-02-25T15:57:04.69149222Z', 'done': True, 'done_reason': 'stop', 'total_duration': 15483951444, 'load_duration': 52944181, 'prompt_eval_count': 64, 'prompt_eval_duration': 200000000, 'eval_count': 92, 'eval_duration': 15205000000, 'message': Message(role='assistant', content='', images=None, tool_calls=None)}, id='run-1a6c5c2c-f650-46c4-a34e-2ca5a069d640-0', usage_metadata={'input_tokens': 64, 'output_tokens': 92, 'total_tokens': 156}), 'parsed': AnswerWithJustification(answer='A pound of bricks is heavier than a pound of feathers.', justification='Both a pound of bricks and a pound of feathers weigh the same, one pound. The difference lies in their volume and density; bricks are much denser than feathers, meaning that while they weigh the same, the bricks will take up less space than the feathers. However, when comparing weight alone, both options weigh the same.'), 'parsing_error': None}\n"
     ]
    }
   ],
   "source": [
    "from typing import Optional\n",
    "\n",
    "from langchain_ollama import ChatOllama\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "base_url = \"http://192.168.31.240:11434\"\n",
    "model_name = \"qwen2.5:7b\"\n",
    "\n",
    "\n",
    "class AnswerWithJustification(BaseModel):\n",
    "    '''An answer to the user question along with justification for the answer.'''\n",
    "\n",
    "    answer: str\n",
    "    justification: Optional[str] = Field(description=\"A justification for the answer.\")\n",
    "\n",
    "\n",
    "llm = ChatOllama(model=model_name, temperature=0, base_url=base_url)\n",
    "structured_llm = llm.with_structured_output(\n",
    "    AnswerWithJustification,\n",
    "    method=\"json_mode\",\n",
    "    include_raw=True\n",
    ")\n",
    "\n",
    "response = structured_llm.invoke(\n",
    "    \"Answer the following question. \"\n",
    "    \"Make sure to return a JSON blob with keys 'answer' and 'justification'.\\n\\n\"\n",
    "    \"What's heavier a pound of bricks or a pound of feathers?\"\n",
    ")\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.messages import HumanMessage\n",
    "from typing import Optional, List\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "\n",
    "# 使用pydantic来定义一个数据模型\n",
    "class NovelData(BaseModel):\n",
    "    text:Optional[str] = Field(description=\"需要润色的小说文本\")\n",
    "    type:Optional[str] = Field(description=\"该段文本的类型， 可能值为： 旁白， 对话\")\n",
    "    character: Optional[str] = Field(description=\"该段文本的说话角色, 请一定要结合上下文推测说话的角色名称。如果是旁白或引申说明，则该字段为空；如果是对话或内心独白，则该字段为说话角色的名称； \")\n",
    "    emotion: Optional[str] = Field(description=\"该段文本的说话角色的情感， 可能的取值为： '快乐', '悲伤', '愤怒', '恐惧', '惊讶', '焦虑', '羞愧', '自豪', '嫉妒', '爱', '失望', '困惑', '希望', '绝望', '同情', '厌恶', '感激', '无聊', '兴奋', '孤独', '内疚', '骄傲', '谦卑', '渴望', '满足', '好奇', '紧张', '宽慰', '疲惫', '振奋'\")\n",
    "\n",
    "class NovelDataListModel(BaseModel):\n",
    "    novel_list: List[NovelData] = Field(description=\"句子列表\")\n",
    "\n",
    "base_url = \"http://192.168.31.240:11434/v1\"\n",
    "model_name = \"qwen2.5:7b\"\n",
    "# model_name = \"llama3:8b\"\n",
    "api_key = \"xxx\"\n",
    "\n",
    "\n",
    "# model = ChatOllama(\n",
    "#     model = model_name,\n",
    "#     temperature = 0,\n",
    "#     base_url = base_url\n",
    "# )\n",
    "\n",
    "model = ChatOpenAI(\n",
    "    temperature=0, \n",
    "    model_name=model_name, \n",
    "    api_key=api_key, \n",
    "    max_tokens= 1024 * 1024,\n",
    "    base_url=base_url)\n",
    "\n",
    "structured_llm = model.with_structured_output(NovelDataListModel)\n",
    "\n",
    "\n",
    "user_prompt_text = \"\"\"\n",
    "请将以下文本转换成有声书脚本。\n",
    "\n",
    "下面是小说片段：\n",
    "`{novel_text}`\n",
    "\"\"\"\n",
    "\n",
    "prompt_template = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\"system\", \"\"\"\n",
    "你是一个专业的有声书脚本转换助手，你的任务是将给定的小说片段转换成适合录制有声书的脚本。\n",
    "        \"\"\"),\n",
    "        (\"human\", user_prompt_text)\n",
    "    ]\n",
    ")\n",
    "\n",
    "\n",
    "chain = prompt_template | structured_llm\n",
    "\n",
    "novel_text = \"\"\"\n",
    "故事开始的那天，我照例是上着班，打扫完一片狼藉的宠物店，走出店门口，在隔壁便利店买了一包五块钱的软白沙，疲惫的靠着墙点了一支烟。\n",
    "店门口的台阶上，一字排开坐了一行人，有老有少，有男有女。有个白嫩的小萝莉，全身汗津津的，bra在校服下若隐若现。青春，真可爱青春。\n",
    "我叼着烟看着那个小萝莉，她一边打电话，一边眨巴眨巴眼睛看我，然后看向路边。我又抽了两口烟，一部宝马停在路边，小萝莉走过去，青春，真可爱青春。\n",
    "小萝莉开了宝马车的门上车，开车的是一个戴墨镜的秃顶大叔，大叔抱住了小萝莉，黑黝黝的手伸向了小萝莉。\n",
    "我在心里骂，禽兽。\n",
    "苦逼啊，我悟了，这个纸醉金迷的花花都市，并不是一个农村孩子的天堂。\n",
    "“张帆，干嘛呢？是不是又偷懒？”一个粗里粗气的声音将我从沉思中惊醒。\n",
    "一扭头，店长何花，老板是她干爹，我们叫她花姐，正怒目冷对着我。\n",
    "我把烟头丢掉，奴颜媚骨的问：“花姐有什么吩咐。”\n",
    "人在屋檐下，不得不低头。\n",
    "“我在店里忙得要死，你倒是闲的很，躲在这里偷懒抽烟，没点上进心，难怪你女朋友跟有钱人跑了。”\n",
    "看着她上下开合的两片薄薄殷红嘴唇，我已经在心里把它骂了一百遍。\n",
    "女友的出轨对我打击无疑是巨大的，偏偏每天来上班还要受到店长的好心提醒：这点事都干不好，难怪你女朋友跟人跑了！给狗洗澡都不会洗，难怪你女朋友跟人跑了！拖地都拖不干净，难怪你女朋友跟人跑了。\n",
    "我女朋友跟人跑了，跟拖地干不干净有毛线关系。\n",
    "“有个客户打电话来，要我们上门给它宠物洗澡！手脚利索点！”她把服务单塞给我。\n",
    "在这家绝望的宠物店，做着绝望的工作，领着着绝望的工资，老板心眼太多，手下心眼太少；加薪是个童话，加班才是现阶段的基本国情。\n",
    "\"\"\"\n",
    "\n",
    "\n",
    "response = chain.invoke({\"novel_text\": novel_text})\n",
    "\n",
    "print(type(response))\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'langchain_core.messages.ai.AIMessage'>\n",
      "[\n",
      "    {\n",
      "        \"text\": \"故事开始的那天，我照例是上着班，打扫完一片狼藉的宠物店，走出店门口，在隔壁便利店买了一包五块钱的软白沙，疲惫的靠着墙点了一支烟。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"店门口的台阶上，一字排开坐了一行人，有老有少，有男有女。有个白嫩的小萝莉，全身汗津津的，bra在校服下若隐若现。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"青春，真可爱青春。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"我叼着烟看着那个小萝莉，她一边打电话，一边眨巴眨巴眼睛看我，然后看向路边。我又抽了两口烟，一部宝马停在路边，小萝莉走过去。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"青春，真可爱青春。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"小萝莉开了宝马车的门上车，开车的是一个戴墨镜的秃顶大叔，大叔抱住了小萝莉，黑黝黝的手伸向了小萝莉。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"我在心里骂，禽兽。\",\n",
      "        \"type\": \"对话\",\n",
      "        \"character\": \"我\",\n",
      "        \"emotion\": \"愤怒\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"苦逼啊，我悟了，这个纸醉金迷的花花都市，并不是一个农村孩子的天堂。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"感慨\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"“张帆，干嘛呢？是不是又偷懒？”一个粗里粗气的声音将我从沉思中惊醒。\",\n",
      "        \"type\": \"对话\",\n",
      "        \"character\": \"何花\",\n",
      "        \"emotion\": \"愤怒\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"一扭头，店长何花，老板是她干爹，我们叫她花姐，正怒目冷对着我。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"我把烟头丢掉，奴颜媚骨的问：‘花姐有什么吩咐。’\",\n",
      "        \"type\": \"对话\",\n",
      "        \"character\": \"我\",\n",
      "        \"emotion\": \"卑微\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"人在屋檐下，不得不低头。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"“我在店里忙得要死，你倒是闲的很，躲在这里偷懒抽烟，没点上进心，难怪你女朋友跟有钱人跑了。”\",\n",
      "        \"type\": \"对话\",\n",
      "        \"character\": \"何花\",\n",
      "        \"emotion\": \"指责\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"看着她上下开合的两片薄薄殷红嘴唇，我已经在心里把它骂了一百遍。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"女友的出轨对我打击无疑是巨大的，偏偏每天来上班还要受到店长的好心提醒：这点事都干不好，难怪你女朋友跟人跑了！给狗洗澡都不会洗，难怪你女朋友跟人跑了！拖地都拖不干净，难怪你女朋友跟人跑了。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"我女朋友跟人跑了，跟拖地干不干净有毛线关系。\",\n",
      "        \"type\": \"对话\",\n",
      "        \"character\": \"我\",\n",
      "        \"emotion\": \"反驳\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"“有个客户打电话来，要我们上门给它宠物洗澡！手脚利索点！”她把服务单塞给我。\",\n",
      "        \"type\": \"对话\",\n",
      "        \"character\": \"何花\",\n",
      "        \"emotion\": \"命令\"\n",
      "    },\n",
      "    {\n",
      "        \"text\": \"在这家绝望的宠物店，做着绝望的工作，领着着绝望的工资，老板心眼太多，手下心眼太少；加薪是个童话，加班才是现阶段的基本国情。\",\n",
      "        \"type\": \"旁白\",\n",
      "        \"character\": null,\n",
      "        \"emotion\": \"叙述\"\n",
      "    }\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.messages import HumanMessage\n",
    "from typing import Optional, List\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "\n",
    "# 使用pydantic来定义一个数据模型\n",
    "class NovelData(BaseModel):\n",
    "    text: str = Field(description=\"需要润色的小说文本\")\n",
    "    type: str = Field(description=\"该段文本的类型， 可能值为： 旁白， 对话\")\n",
    "    character: Optional[str] = Field(description=\"该段文本的说话角色, 请一定要结合上下文推测说话的角色名称。如果是旁白或引申说明，则该字段为空；如果是对话或内心独白，则该字段为说话角色的名称； \")\n",
    "    emotion: Optional[str] = Field(description=\"该段文本的说话角色的情感， 可能的取值为： '快乐', '悲伤', '愤怒', '恐惧', '惊讶', '焦虑', '羞愧', '自豪', '嫉妒', '爱', '失望', '困惑', '希望', '绝望', '同情', '厌恶', '感激', '无聊', '兴奋', '孤独', '内疚', '骄傲', '谦卑', '渴望', '满足', '好奇', '紧张', '宽慰', '疲惫', '振奋'\")\n",
    "\n",
    "class NovelDataListModel(BaseModel):\n",
    "    novel_list: List[NovelData] = Field(description=\"句子列表\")\n",
    "\n",
    "base_url = \"http://192.168.31.240:11434/v1\"\n",
    "api_key = \"sk-xxx\"\n",
    "model_name = \"qwen2.5:7b\"\n",
    "# model_name = \"qwen2.5:7b-128kin-maxout\"\n",
    "# model_name = \"qwen2.5:1.5b-instruct\"\n",
    "# model_name = \"qwen2.5:1.5b-instruct-128kin-maxout\"\n",
    "# model_name = \"deepseek-r1:14b\"\n",
    "\n",
    "human_message = HumanMessage(content = '你好, 你是谁?')\n",
    "\n",
    "\n",
    "model = ChatOpenAI(\n",
    "    temperature=0, \n",
    "    model_name=model_name, \n",
    "    api_key=api_key, \n",
    "    max_tokens= 1024 * 1024 * 1024,\n",
    "    base_url=base_url)\n",
    "\n",
    "structured_llm = model.with_structured_output(NovelData)\n",
    "# structured_llm = model.with_structured_output(NovelDataListModel)\n",
    "\n",
    "user_prompt_text = \"\"\"\n",
    "请将以下文本转换成适合有声书的脚本格式。\n",
    "\n",
    "参考以下示例：\n",
    "\n",
    "## 示例1\n",
    "原文: “张帆，干嘛呢？是不是又偷懒？”一个粗里粗气的声音将我从沉思中惊醒。\n",
    "输出: \n",
    "[\n",
    "    {{\n",
    "        \"text\": \"张帆，干嘛呢？是不是又偷懒？\",\n",
    "        \"type\": \"对话\",\n",
    "        \"character\": \"荷花\",\n",
    "        \"emotion\": \"愤怒\"\n",
    "    }},\n",
    "    {{\n",
    "        \"text\": \"一个粗里粗气的声音将我从沉思中惊醒。\",\n",
    "        \"type\": \"旁白\",\n",
    "        \"character\": null,\n",
    "        \"emotion\": \"厌恶\"\n",
    "    }}\n",
    "]\n",
    "\n",
    "\n",
    "## 示例2\n",
    "原文: “这不可能！”李明大声喊道，他的脸因为愤怒而涨得通红。“你一定在骗我。”\n",
    "输出: \n",
    "[\n",
    "    {{\n",
    "        \"text\": \"这不可能！\",\n",
    "        \"type\": \"对话\",\n",
    "        \"character\": \"李明\",\n",
    "        \"emotion\": \"愤怒\"\n",
    "    }},\n",
    "    {{\n",
    "        \"text\": \"李明大声喊道，他的脸因为愤怒而涨得通红。\",\n",
    "        \"type\": \"旁白\",\n",
    "        \"character\": null,\n",
    "        \"emotion\": \"厌恶\"\n",
    "    }},\n",
    "    {{\n",
    "        \"text\": \"你一定在骗我。\",\n",
    "        \"type\": \"对话\",\n",
    "        \"character\": \"李明\",\n",
    "        \"emotion\": \"愤怒\"\n",
    "    }}\n",
    "]\n",
    "\n",
    "\n",
    "## 示例3\n",
    "原文: “你确定这样做是对的吗？”王丽问，她的声音充满了不确定。\n",
    "输出: \n",
    "[\n",
    "    {{\n",
    "        \"text\": \"你确定这样做是对的吗？\",\n",
    "        \"type\": \"对话\",\n",
    "        \"character\": \"王丽\",\n",
    "        \"emotion\": \"惊讶\"\n",
    "    }},\n",
    "    {{\n",
    "        \"text\": \"王丽问，她的声音充满了不确定。\",\n",
    "        \"type\": \"旁白\",\n",
    "        \"character\": null,\n",
    "        \"emotion\": \"惊讶\"\n",
    "    }}\n",
    "]\n",
    "\n",
    "## 示例4\n",
    "原文: “我们走吧。”他轻轻地说，同时伸出手来拉她。\n",
    "输出: \n",
    "[\n",
    "    {{\n",
    "        \"text\": \"我们走吧。\",\n",
    "        \"type\": \"对话\",\n",
    "        \"character\": \"他\",\n",
    "        \"emotion\": \"振奋\"\n",
    "    }},\n",
    "    {{\n",
    "        \"text\": \"他轻轻地说，同时伸出手来拉她。\",\n",
    "        \"type\": \"旁白\",\n",
    "        \"character\": null,\n",
    "        \"emotion\": \"振奋\"\n",
    "    }}\n",
    "]\n",
    "\n",
    "## 示例5\n",
    "原文: 我把烟头丢掉，奴颜媚骨的问：“花姐有什么吩咐。”\n",
    "输出: \n",
    "[\n",
    "    {{\n",
    "        \"text\": \"我把烟头丢掉，奴颜媚骨的问\",\n",
    "        \"type\": \"旁白\",\n",
    "        \"character\": \"我\",\n",
    "        \"emotion\": \"谦卑\"\n",
    "    }},\n",
    "    {{\n",
    "        \"text\": \"花姐有什么吩咐。\",\n",
    "        \"type\": \"对话\",\n",
    "        \"character\": \"我\",\n",
    "        \"emotion\": \"内疚\"\n",
    "    }}\n",
    "]\n",
    "\n",
    "下面是小说片段：\n",
    "`{novel_text}`\n",
    "\"\"\"\n",
    "\n",
    "prompt_template = ChatPromptTemplate(\n",
    "    [\n",
    "        (\"system\", \"\"\"\n",
    "你是一个专业的有声书脚本转换助手，你的任务是将给定的小说片段转换成适合录制有声书的脚本。请按照以下规则进行转换：\n",
    "\n",
    "1. **区分对话和旁白**：\n",
    "   - 识别并分离文本中的人物对话（通常位于双引号内）和旁白（描述性文字）。对话部分应被特别标注出来，以便于后续的声音演绎。\n",
    "   \n",
    "2. **角色对话处理**：\n",
    "   - 对于每一句人物对话，请指明说话者的身份（如果已知），并建议适当的情绪、语气或音调变化来表达该句话的情感背景。例如，“愤怒地说”、“轻柔地问”等。\n",
    "   - 如果原文中没有明确指出是谁在说话，请根据上下文合理推测，并在脚本中标注出可能的说话者。\n",
    "   - 如果一个句子中包含了对话(“”中包含的一般是对话), 需要将该句子拆分成'对话'和'旁白'。\n",
    "\n",
    "3. **旁白处理**：\n",
    "   - 将旁白部分用叙述的方式呈现，确保语言流畅且易于理解。旁白应当用来建立场景、描述动作或解释事件，帮助听众更好地想象故事的发展。\n",
    "   - 注意旁白中的情感色彩，如紧张、轻松、悲伤等，并通过语速、语调的变化传达给听众。\n",
    "\n",
    "4. **特殊说明**：\n",
    "   - 如果文本中有任何特殊的格式化要求（如强调某些词语、使用特定的停顿等），请在脚本中明确指出。\n",
    "   - 对于不常见的词汇或者专有名词，考虑添加发音指导以确保正确朗读。\n",
    "\n",
    "请严格按照上述指导原则执行转换任务，确保最终生成的有声书脚本能准确反映原文的情感和氛围，同时为听众提供愉悦的听觉体验。\n",
    "\n",
    "        \"\"\"),\n",
    "        (\"user\", user_prompt_text)\n",
    "    ]\n",
    ")\n",
    "\n",
    "\n",
    "\n",
    "# chain = prompt_template | structured_llm\n",
    "chain = prompt_template | model\n",
    "\n",
    "novel_text = \"\"\"\n",
    "故事开始的那天，我照例是上着班，打扫完一片狼藉的宠物店，走出店门口，在隔壁便利店买了一包五块钱的软白沙，疲惫的靠着墙点了一支烟。\n",
    "店门口的台阶上，一字排开坐了一行人，有老有少，有男有女。有个白嫩的小萝莉，全身汗津津的，bra在校服下若隐若现。青春，真可爱青春。\n",
    "我叼着烟看着那个小萝莉，她一边打电话，一边眨巴眨巴眼睛看我，然后看向路边。我又抽了两口烟，一部宝马停在路边，小萝莉走过去，青春，真可爱青春。\n",
    "小萝莉开了宝马车的门上车，开车的是一个戴墨镜的秃顶大叔，大叔抱住了小萝莉，黑黝黝的手伸向了小萝莉。\n",
    "我在心里骂，禽兽。\n",
    "苦逼啊，我悟了，这个纸醉金迷的花花都市，并不是一个农村孩子的天堂。\n",
    "“张帆，干嘛呢？是不是又偷懒？”一个粗里粗气的声音将我从沉思中惊醒。\n",
    "一扭头，店长何花，老板是她干爹，我们叫她花姐，正怒目冷对着我。\n",
    "我把烟头丢掉，奴颜媚骨的问：“花姐有什么吩咐。”\n",
    "人在屋檐下，不得不低头。\n",
    "“我在店里忙得要死，你倒是闲的很，躲在这里偷懒抽烟，没点上进心，难怪你女朋友跟有钱人跑了。”\n",
    "看着她上下开合的两片薄薄殷红嘴唇，我已经在心里把它骂了一百遍。\n",
    "女友的出轨对我打击无疑是巨大的，偏偏每天来上班还要受到店长的好心提醒：这点事都干不好，难怪你女朋友跟人跑了！给狗洗澡都不会洗，难怪你女朋友跟人跑了！拖地都拖不干净，难怪你女朋友跟人跑了。\n",
    "我女朋友跟人跑了，跟拖地干不干净有毛线关系。\n",
    "“有个客户打电话来，要我们上门给它宠物洗澡！手脚利索点！”她把服务单塞给我。\n",
    "在这家绝望的宠物店，做着绝望的工作，领着着绝望的工资，老板心眼太多，手下心眼太少；加薪是个童话，加班才是现阶段的基本国情。\n",
    "\"\"\"\n",
    "\n",
    "\n",
    "response = chain.invoke({\"novel_text\": novel_text})\n",
    "print(type(response))\n",
    "print(response.content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pydantic-ai",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
